package vpc.ext.vstack.util;

import vpc.core.types.Type;
import vpc.core.base.*;
import vpc.core.decl.Method;
import vpc.core.decl.Method.Temporary;
import vpc.ext.vstack.util.Debug;
import vpc.hil.parser.Token;
import vpc.tir.TIRRep;
import vpc.tir.TIRUtil;
import java.util.List;
import vpc.hil.Register;
import vpc.hil.Interrupt;
import vpc.core.Value;

/**
 * @author Philip Russell
 *
 */

public class DataHandler {

	private static Debug.Flag debugFlag = Debug.Flag.DATAHANDLER;
	
	/* The sizes below enumerate every internal Virgil value
	 * that is placed on the stack (at least according to VPC).
	 * So far, this includes:
	 * - Primitive types (boolean, char, int32)
	 * - References (to arrays, components, etc.)
	 * - Pairs of references (for delegate arguments of the form (object, function)
	 */
	
	// Number of bits for a Virgil Int32 type
	private static int INT32_SIZE = 32;
	// Number of bits for a Virgil Boolean type
	private static int BOOL_SIZE = 8;
	// Number of bits for a Virgil Character type
	private static int CHAR_SIZE = 8;
	// Number of bits for a Virgil Reference type
	private static int REFERENCE_SIZE = 32;
	// Number of bits for a pair of References
	private static int PAIR_REFERENCE_SIZE = 64;
	// Size of the return address pushed onto the stack for each method call
	private static int RETURN_SIZE = 8;
	// Size of the interrupt mask register pushed onto the stack for each interrupt
	private static int INTERRUPT_REG_SIZE = 8;
	
	
	/**
	 * Converts the "bit position" indicated by the given Token to a base 10
	 * value. Example: if position is equal to "5", then 32 (2^5) is returned.
	 * @param t The Token representing the bit position in a bit vector (ie register)
	 * @return The base 10 value of that bits position.
	 */
	public static long bitToBase10(Token t) {
		int exponent = Integer.valueOf(t.toString());
		long base = 1;
		while (exponent > 0) {
			base <<= 1;
			exponent -= 1;
		}
		return base;
	}
	
	/**
	 * Examines the Type and returns the size of the stack push for
	 * the argument value.
	 * @param t The Type to be examined
	 * @return The stack push value, in bits, for this Type.
	 * @throws UnhandledTypeException If the argument is of an unrecognized Type.
	 * {@link vpc.core.types.Type}
	 */
	public static int numBits(Type t) throws VstackException.UnhandledTypeException {
		
		int numBits = 0;
				
		if (t == PrimChar.TYPE) {
			//This is a char?
			numBits = CHAR_SIZE;
		} 
		
		else if (t == PrimBool.TYPE) {
			numBits = BOOL_SIZE;
		}
		
		else if (t == PrimInt32.TYPE) {
			numBits = INT32_SIZE;
		}
				
		else if (t instanceof PrimRaw.IType) {
			//PrimRaw type, need to get the number of bits
			int width = ((PrimRaw.IType)t).width;
			numBits = width;
		}
			
		else if (t.isArray()) {
			numBits = REFERENCE_SIZE;
		}
		
//		else if (t.isComponent()) {
//			numBits = REFERENCE_SIZE;
//		}
		
		else if (t.isReference()) {
			numBits = REFERENCE_SIZE;
		}
		
		else if (t.isDelegate()) {
			numBits = PAIR_REFERENCE_SIZE;
		}
		
		else {
			throw new VstackException.UnhandledTypeException("Unrecognized type = " + t.name);
		}
			
		return numBits;
	}
	
	public static int addressSize() {
		return DataHandler.RETURN_SIZE;
	}
	
	public static int interruptSize() {
		return DataHandler.INTERRUPT_REG_SIZE;
	}
	
	/**
	 * TODO: the final purpose of this method is to return the number of bytes
	 * pushed onto the stack as a result of calling this method. getStackPush does
	 * not analyze the body of the method for information about the interrupt state.
	 * 
	 * @param m
	 *            The method to be analyzed.
	 * @return The maximum number of bytes pushed onto the stack frame as a
	 *         result of calling this method.
	 */
	public static int getStackPush(Method m) {

		Debug.Flag localFlag = Debug.Flag.GET_STACK_PUSH;

		TIRRep rep = TIRUtil.getRep(m);		

		// Every method call translates to a basic set of data that is pushed
		// onto the stack regardless of parameters and temporaries. This includes:
		// - the return address
		// Initialize the stack push size to be this basic set of data.
		int pushSize = DataHandler.addressSize();

		Debug.println(Debug.seperator1, localFlag);
		Debug.println("Method " + m.getFullName(), localFlag);
		Debug.println("Return address size = " + DataHandler.addressSize(), localFlag);

		// ALGORITHM:
		// For each parameter supplied to this procedure call
		//		Analyze the type of the parameter
		//		If the type is known, then translate the type to a numerical push value
		//		Add this value to the stack push
		for (Temporary<Type> arg : rep.getParams()) {
			
			Type type = arg.getType();
			Debug.print("\tArg : name = " + arg.getName() + ", type = " + type.name, localFlag);

			try {
				int argSize = DataHandler.numBits(type);
				pushSize += argSize;
				Debug.println(", size = " + argSize, localFlag);

			} catch (VstackException.UnhandledTypeException e) {
				Debug.println(e.getMessage(), localFlag);
			}
		}

		// ALGORITHM
		// Same as the above, but for each temporary value allocated by this procedure call
		for (Temporary<Type> t : rep.getTemps()) {
			Type type = t.getType();

			Debug.print("\tTemp: id = " + t.getName() + ", type = "
					+ type.name, localFlag);
			try {
				int tempSize = DataHandler.numBits(type);
				pushSize += tempSize;
				Debug.println(", size = " + tempSize, localFlag);
			} catch (VstackException.UnhandledTypeException e) {
				Debug.println(e.getMessage(), localFlag);
			}
		}

		Debug.println("\tTotal size = " + pushSize, localFlag);
		
		// Return the total size of the stack frame push
		return pushSize;
	}
	
}
